﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using FinPlusCommon;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class QLEngineBondCurves : FinPlusComponent
    {
        private enum Params { CurveName, LinkedCurve };
        private enum BuildEvents { Update, Refresh, UpdateAndRefresh }
        private readonly IFinPlusComp _bondCurveBuildConn, _instrumentBuildConn, _instrumentConn;
        private readonly QLAnalytics _analytics;
        private readonly BondCurves _bondCurves;
        private readonly BuildEvents _buildEvents;
   
        //construct
        public QLEngineBondCurves(IFinPlusComp analytics, IFinPlusComp bondCurveBuilds, IFinPlusComp instrumentBuilds, IFinPlusComp instruments, string buildEvents)
        {
            _analytics = (QLAnalytics)analytics;
            _bondCurveBuildConn = bondCurveBuilds;
            _instrumentBuildConn = instrumentBuilds;
            _instrumentConn = instruments;
            _buildEvents = u.EnumParse<BuildEvents>(buildEvents, BuildEvents.UpdateAndRefresh);
            _bondCurves = new BondCurves();

            foreach (var bondCurveBuild in _bondCurveBuildConn.Adaptor.Values)
            {
                var curveName = bondCurveBuild[Params.CurveName.ToString()].ToString();
                _bondCurves.TryAdd(curveName, new BondCurve(_analytics, curveName, bondCurveBuild, _instrumentBuildConn.Adaptor,
                     _instrumentConn.Adaptor.ToList(Params.LinkedCurve.ToString(), curveName, ','), _buildEvents));
            }

            IsValid = true;
        }

        //common control interface
        public override void Dispose()
        {
            if(_instrumentConn != null)
                _instrumentConn.Dispose(); 

            if(_bondCurveBuildConn != null)
                _bondCurveBuildConn.Dispose();

            if (_instrumentBuildConn != null)
                _instrumentBuildConn.Dispose(); 
        }

        //all helper class of component FinPlusEngineYieldCurves 
        private class BondCurves : ConcurrentDictionary<string, BondCurve> { }

        private class BondCurve : ConcurrentDictionary<string, IRate>
        {
            private enum Params { LevelUpdate, Refresh, CurveName, Shock, FunctionName, Instrument, NumZCM, NumBond, DayCount, Tolerance, Holidays };
            private enum BuildType { ZCMBuild, FixedRateBondBuild, Unknown };

            private IList<IFunction> _instruments;
            private FinPlusLevel _level;
            private QLAnalytics _analytics;
            private IFunctions _instrumentBuilds;
            private IFunction _bondCurveBuild;
            private BuildEvents _buildEvents;
            private int _numZcm, _numBond, _zcmCount, _bondCount;
            private string _curveName, _dayCount, _holidays;
            private double _tolerance;

            public BondCurve(QLAnalytics analytics, string curveName, IFunction bondCurveBuild, IFunctions instrumentBuilds, IList<IFunction> instruments, BuildEvents buildEvents)
            {
                _analytics = analytics;
                _curveName = curveName;
                _bondCurveBuild = bondCurveBuild;
                _instrumentBuilds = instrumentBuilds;
                _instruments = instruments;
                _level = new FinPlusLevel(analytics.BuySell);
                _dayCount = _bondCurveBuild[Params.DayCount.ToString()].ToString();
                _tolerance = double.Parse(_bondCurveBuild[Params.Tolerance.ToString()].ToString());
                _holidays = _bondCurveBuild[Params.Holidays.ToString()].ToString();
                _numZcm = (int)_bondCurveBuild.Value(Params.NumZCM.ToString());
                _numBond = (int)_bondCurveBuild.Value(Params.NumBond.ToString());
                _buildEvents = buildEvents;
                Initialise();
            }

            public void Initialise()
            {
                foreach (var instrument in _instruments)
                {
                    if (_buildEvents == BuildEvents.Update || _buildEvents == BuildEvents.UpdateAndRefresh) 
                        instrument.Get(Params.LevelUpdate.ToString(), new Function(), Curve_Update);
                    
                    if (_buildEvents == BuildEvents.Refresh || _buildEvents == BuildEvents.UpdateAndRefresh)
                        instrument.Get(Params.Refresh.ToString(), string.Empty, Curve_Refresh);

                    instrument.Get(Params.Shock.ToString(), Curve_Shock);

                    var instrumentName = instrument[Params.Instrument.ToString()].ToString();
                    var instrumentBuild = _instrumentBuilds.First("Name='Instrument' And Value='" + instrumentName + "'");

                    if (instrumentBuild != null)
                    {
                        this[instrumentName] = AddRate(instrumentName, instrument, instrumentBuild);
                        instrument.Get(Params.Refresh.ToString(), string.Empty).Set(instrumentName);
                    }
                }
            }

            //private
            private IRate AddRate(string instrumentName, IFunction instrument, IFunction instrumentBuild)
            {
                 switch (u.EnumParse<BuildType>(instrumentBuild[Params.FunctionName.ToString()].ToString(), BuildType.Unknown))
                {
                    case BuildType.ZCMBuild: _zcmCount++; return new ZCMRate(_analytics, instrumentName, instrument, instrumentBuild);
                    case BuildType.FixedRateBondBuild: _bondCount++; return new Bond(_analytics, instrumentName, instrument, instrumentBuild);
                    case BuildType.Unknown: break; //TODO 
                }

                return null;
            }

            private void BuildCurve()
            {
                lock (_analytics.QL)
                    _analytics.QL.BondCurve(_analytics.MarketName, _curveName, _analytics.DiscountCurve, _analytics.AsOf, Rates(_analytics.BuySell), _dayCount, _tolerance, _holidays);
            }

            private string Rates(string buySell)
            {
                return string.Join(",", this.Values.Select(f => f.Build(buySell)).ToArray<string>());
            }

            private bool IsValid()
            {
                if (_numZcm != _zcmCount || _numBond != _bondCount)
                    return false;

                foreach (var rate in this.Values)
                    if (Double.IsNaN(rate.Value))
                        return false;

                return true;
            }

            //events
            private void Curve_Update(object s, PropertyChangedEventArgs a)
            {
                var level = (IFunction)((IItem)s).Object;
                var instrument = level[Params.Instrument.ToString()].ToString();

                if (this[instrument].Update(level) && IsValid()) 
                    BuildCurve();
            }

            private void Curve_Refresh(object s, PropertyChangedEventArgs a)
            {
                if (IsValid() && (((IItem)s).Value.Equals(_curveName)))
                    BuildCurve();
            }

            private void Curve_Shock(object s, PropertyChangedEventArgs a)
            {
                var shock = (IFunction)((IItem)s).Object;
                var curveName = shock[Params.CurveName.ToString()].ToString();
                if (curveName.Equals(_curveName))
                {
                    var instrumentName = shock[Params.Instrument.ToString()].ToString();
                    var value = (int)shock.Get(Params.Shock.ToString()).Object;
                    this[instrumentName].Shock = value;
                    if (value != 0 && IsValid()) BuildCurve();
                }
            }
        }

        private interface IRate
        {
            double Value { get; }
            int Shock { get; set; }

            bool Update(IFunction level);
            string Build(string buySell);
        }

        private class ZCMRate : IRate
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }

            private enum Params {ShockUnit, BuySell, Level, Maturity, SettlementDays, DayCount, BizConv, Holidays };
            
            private FinPlusLevel _level;
            private QLAnalytics _analytics;
            private IFunction _instrumentBuild, _instrument;
            private string _instrumentName, _tenor, _dayCount, _bizConv, _holidays;
            private int _settlementDays;
            private double _shockUnit;

            public ZCMRate(QLAnalytics analytics, string instrumentName, IFunction instrument, IFunction instrumentBuild)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument = instrument;
                _instrumentBuild = instrumentBuild;
                
                _level = new FinPlusLevel(analytics.BuySell);
                _tenor = _instrumentBuild[Params.Maturity.ToString()].ToString();
                _settlementDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _dayCount = _instrumentBuild[Params.DayCount.ToString()].ToString();
                _bizConv = _instrumentBuild[Params.BizConv.ToString()].ToString();
                _holidays = _instrumentBuild[Params.Holidays.ToString()].ToString();
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                lock (_analytics.QL)
                    return _analytics.QL.DepoRate(_analytics.MarketName, _instrumentName, _level.Value + _shockUnit * Shock, _tenor, _settlementDays, _dayCount, _bizConv, _holidays);
            }

            public bool Update(IFunction level)
            {
                return _level.LevelUpdate(level);
            }
        }

        private class Bond : IRate
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }

            private enum Params { ShockUnit }
            private FinPlusLevel _level;
            private QLAnalytics _analytics;
            private IFunction _instrumentBuild, _instrument;
            private string _instrumentName;
            private double _shockUnit;

            public Bond(QLAnalytics analytics, string instrumentName, IFunction instrument, IFunction instrumentBuild)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument = instrument;
                _instrumentBuild = instrumentBuild;
                _level = new FinPlusLevel(analytics.BuySell);
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                //TODO adj re ZCM if shock
                return _instrumentName;
            }

            public bool Update(IFunction level)
            {
                return _level.LevelUpdate(level);
            }
        }
      }
}
